Skip to content

Skip Sentry capture when token refresh fails with 401#263

Merged
gianfrancopiana merged 1 commit into
mainfrom
claude/skip-sentry-401-refresh-b6a73b28
Jun 4, 2026
Merged

Skip Sentry capture when token refresh fails with 401#263
gianfrancopiana merged 1 commit into
mainfrom
claude/skip-sentry-401-refresh-b6a73b28

Conversation

@sentry

@sentry sentry Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

What

Skip Sentry.captureException when a token refresh fails with UnauthorizedError (401) in useAPIRequest. Instead, log with console.warn and proceed to logout — mirroring the pattern already used in fetchCreatorStatus.

Why

Gumroad updated required OAuth scopes, invalidating older stored tokens. Both the access token and refresh token return 401 because the grant lacks the new scopes. This is an expected re-authentication flow: logout is the correct recovery, not a Sentry-reportable bug. The previous behavior captured every one of these as a Sentry exception, causing a spike of ~2.6k noise events on April 19.

The fix distinguishes between:

  • UnauthorizedError during refresh → expected (token expired/revoked/missing scopes) → console.warn, no Sentry
  • KeychainUnavailableError during refresh → device locked → rethrow original error, no Sentry, no logout
  • Any other error during refresh → unexpected → Sentry.captureException, then logout

Test results

All 15 useAPIRequest tests pass, including a new test covering the UnauthorizedError refresh path:

PASS tests/lib/use-api-request.test.tsx
  useAPIRequest
    ✓ returns data on a successful request without invoking refresh or logout
    ✓ attempts refresh on 401 and retries with the new token
    ✓ propagates the original 401 without logging out when refresh fails with KeychainUnavailableError
    ✓ logs out without reporting to Sentry when refresh fails with UnauthorizedError
    ✓ logs out when refresh fails with a raw keychain error (write-side after server rotation)
    ✓ logs out and reports to Sentry with auth_path tag when refresh fails with a non-keychain error
    ...

Tests: 15 passed, 15 total

Generated by Claude Sonnet 4. Prompted to fix the issue described in GUMROAD-MOBILE-2D: suppress Sentry capture for UnauthorizedError during token refresh in useAPIRequest.


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Note

Low Risk
Narrows Sentry reporting for a known auth path; logout behavior for failed refresh is unchanged except for expected 401s.

Overview
useAPIRequest no longer sends Sentry.captureException when token refresh fails with UnauthorizedError. Those cases are treated as expected re-auth (e.g. revoked or out-of-scope tokens): console.warn, then logout, with the original 401 still surfaced to the caller.

Unexpected refresh failures (anything other than KeychainUnavailableError or UnauthorizedError) still go to Sentry with the auth_path: refresh_failed tag before logout. A test asserts the 401-on-refresh path logs out without Sentry noise.

Reviewed by Cursor Bugbot for commit 9dcab47. Bugbot is set up for automated code reviews on this repo. Configure here.

When a token refresh fails with UnauthorizedError (401), this is an
expected re-authentication flow — the refresh token is expired, revoked,
or lacks required OAuth scopes. The app correctly logs out the user,
but was also capturing this to Sentry, generating thousands of noise
events (2.6k spike on April 19 after a scope change).

Mirror the pattern already used in fetchCreatorStatus: console.warn
for UnauthorizedError, Sentry.captureException only for unexpected
errors.

Fixes GUMROAD-MOBILE-2D
@greptile-apps

greptile-apps Bot commented Jun 4, 2026

Copy link
Copy Markdown

Greptile Summary

This PR suppresses Sentry noise by distinguishing expected UnauthorizedError failures during token refresh (expired/revoked/scope-mismatched tokens) from unexpected failures. The change mirrors the pattern already established in fetchCreatorStatus in auth-context.tsx.

  • lib/request.ts: Wraps the existing Sentry.captureException call with an UnauthorizedError guard; the UnauthorizedError path logs via console.warn and still proceeds to logout(), while any other non-keychain error continues to capture to Sentry.
  • tests/lib/use-api-request.test.tsx: Adds one new test verifying that logout is invoked and Sentry.captureException is not when the refresh endpoint returns 401.

Confidence Score: 5/5

Safe to merge — the change is a targeted two-branch addition to an existing error handler with no effect on the happy path or the KeychainUnavailableError path.

The diff is four lines in the production code and a single new test. The three distinct refresh-failure paths are each individually tested, and the new UnauthorizedError branch correctly falls through to logout() just as the pre-existing catch-all did. The pattern is consistent with how fetchCreatorStatus in auth-context.tsx already handles the same case.

No files require special attention.

Important Files Changed

Filename Overview
lib/request.ts Adds an UnauthorizedError branch in the refresh-failure handler to skip Sentry and emit console.warn instead; logout still executes on this path
tests/lib/use-api-request.test.tsx Adds one new test asserting logout is called and Sentry is not when the refresh throws UnauthorizedError; all existing tests preserved

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[requestAPI returns 401] --> B{UnauthorizedError?}
    B -- No --> C[throw error]
    B -- Yes --> D[refreshToken]
    D -- success --> E[retry requestAPI with new token]
    D -->|KeychainUnavailableError| F[throw original error - no logout, no Sentry]
    D -->|UnauthorizedError| G[console.warn then logout then throw original error]
    D -->|other error| H[Sentry.captureException then logout then throw original error]
Loading

Reviews (1): Last reviewed commit: "Skip Sentry capture when token refresh f..." | Re-trigger Greptile

@gumclaw gumclaw self-assigned this Jun 4, 2026
@gianfrancopiana gianfrancopiana merged commit d81f494 into main Jun 4, 2026
2 checks passed
@gianfrancopiana gianfrancopiana deleted the claude/skip-sentry-401-refresh-b6a73b28 branch June 4, 2026 21:03
@gumclaw gumclaw assigned nyomanjyotisa and unassigned gumclaw Jun 4, 2026
@gumclaw

gumclaw commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Assigning @nyomanjyotisa per Gianfranco.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants